package com.hotec.PROJECT_NAME.modules.wx.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hotec.PROJECT_NAME.component.basic.ConstantKey;
import com.hotec.PROJECT_NAME.component.exception.exc.BizException;
import com.hotec.PROJECT_NAME.modules.sys.entity.SysAccount;
import com.hotec.PROJECT_NAME.modules.sys.entity.SysDict;
import com.hotec.PROJECT_NAME.modules.sys.service.SysAccountService;
import com.hotec.PROJECT_NAME.modules.sys.service.SysDictService;
import com.hotec.PROJECT_NAME.modules.wx.args.WxConsumeRegistryArgs;
import com.hotec.PROJECT_NAME.modules.wx.args.WxLoginArgs;
import com.hotec.PROJECT_NAME.utils.UtilsAccount;
import com.hotec.PROJECT_NAME.utils.UtilsJwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Transactional
public class WxService {
    @Value("${wx.applet.consume.app_id}")
    private String wxConsumeAppId;

    @Value("${wx.applet.consume.app_secret}")
    private String wxConsumeAppSecret;

    @Value("${wx.applet.shop.app_id}")
    private String wxShopAppId;

    @Value("${wx.applet.shop.app_secret}")
    private String wxShopAppSecret;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SysAccountService sysAccountService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private SysDictService sysDictService;

    private Map<String, Object> convertWxResponseAndValid(String wxResponseStr) {
        Map<String, Object> wxResponseMap = JSONUtil.toBean(wxResponseStr, new TypeReference<Map<String, Object>>() {
        }, true);

        Integer errcode = MapUtil.getInt(wxResponseMap, "errcode");

        if(ObjectUtil.isNotNull(errcode) && errcode != 0){
            String errmsg = MapUtil.getStr(wxResponseMap, "errmsg");

            throw new BizException(StrUtil.format("错误码：{}。错误信息：{}", errcode, errmsg));
        }

        return wxResponseMap;
    }

    private String getAccessToken() {
        String wxAccessTokenKey = "wx_access_token";

        ValueOperations valueOperations = redisTemplate.opsForValue();

        Object cacheAccessToken = valueOperations.get(wxAccessTokenKey);

        if(ObjectUtil.isNotNull(cacheAccessToken)) return String.valueOf(cacheAccessToken);

        LinkedHashMap<String, Object> queryTokenCondition = Maps.newLinkedHashMap();
        queryTokenCondition.put("grant_type", "client_credential");
        queryTokenCondition.put("appid", wxConsumeAppId);
        queryTokenCondition.put("secret", wxConsumeAppSecret);

        String wxResponseStr = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token", queryTokenCondition);

        Map<String, Object> wxResponseMap = convertWxResponseAndValid(wxResponseStr);

        String accessToken = MapUtil.getStr(wxResponseMap, "access_token");

        Long expiresIn = MapUtil.getLong(wxResponseMap, "expires_in");

        valueOperations.set(wxAccessTokenKey, accessToken, expiresIn, TimeUnit.SECONDS);

        return accessToken;
    }

    private Map<String, Object> getOpenIdByCode(String code, String appid, String secret) {
        LinkedHashMap<String, Object> querySessionCondition = Maps.newLinkedHashMap();
        querySessionCondition.put("appid", appid);
        querySessionCondition.put("secret", secret);
        querySessionCondition.put("js_code", code);
        querySessionCondition.put("grant_type", "authorization_code");

        String wxResponseStr = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session", querySessionCondition);

        return convertWxResponseAndValid(wxResponseStr);
    }

    public Map<String, Object> consumeGetOpenIdByCode(String code) {
        return getOpenIdByCode(code, wxConsumeAppId, wxConsumeAppSecret);
    }

    public Map<Object, Object> loginByOpenId(WxLoginArgs wxLoginArgs) {
        Assert.notBlank(wxLoginArgs.getOpenId(), "微信标识为空");

        List<SysAccount> sysAccounts = sysAccountService.queryAll(new SysAccount().setOpenId(wxLoginArgs.getOpenId()));

        if(CollectionUtil.isEmpty(sysAccounts)) throw new BizException(401, "用户不存在");

        Assert.isTrue(sysAccounts.size() == 1, "查询到多个用户身份");

        return createWxLoginSuccessfulResponse(sysAccounts.get(0));
    }

    public Map<Object, Object> consumeRegistry(WxConsumeRegistryArgs wxRegistryArgs) {
        String accessToken = getAccessToken();

        String requestUrl = StrUtil.format("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={}", accessToken);

        LinkedHashMap<String, Object> queryPhoneCondition = Maps.newLinkedHashMap();
        queryPhoneCondition.put("code", wxRegistryArgs.getCode());

        String wxResponseString = HttpUtil.post(requestUrl, JSON.toJSONString(queryPhoneCondition));

        Map<String, Object> wxResponseMap = convertWxResponseAndValid(wxResponseString);

        Map<String, Object> phoneInfo = MapUtil.get(wxResponseMap, "phone_info", new TypeReference<Map<String, Object>>() {
        });

        String purePhoneNumber = MapUtil.getStr(phoneInfo, "purePhoneNumber");

        String phoneNumber = MapUtil.getStr(phoneInfo, "phoneNumber");

        String phone = StrUtil.blankToDefault(purePhoneNumber, phoneNumber);

        String openId = wxRegistryArgs.getOpenId();

        //查询用户
        SysAccount sysAccount = sysAccountService.getOne(new SysAccount().setMobile(phone));

        if(ObjectUtil.isNotNull(sysAccount)){
            sysAccount.setOpenId(openId);
        }else{
            sysAccount = new SysAccount()
                    .setMobile(phone)
                    .setOpenId(openId)
                    .setNickname(phone);
        }

        sysAccountService.save(sysAccount);

        Assert.notNull(sysAccount, "用户注册异常");

        return createWxLoginSuccessfulResponse(sysAccount);
    }

    private Map<Object, Object> createWxLoginSuccessfulResponse(SysAccount sysAccount) {
        Map<String, Object> tokenInfoMap = Maps.newLinkedHashMap();
        tokenInfoMap.put(ConstantKey.ACCOUNT_ID, sysAccount.getId());
        tokenInfoMap.put("roles", Lists.newArrayList());

        String token = UtilsJwt.createToken(tokenInfoMap);

        Map<Object, Object> responseMap = Maps.newLinkedHashMap();
        responseMap.put("token", ConstantKey.TOKEN_PRE + token);
        responseMap.put("userInfo", sysAccount);
        return responseMap;
    }

    public Map<String, Object> shopGetOpenIdByCode(String code) {
        return getOpenIdByCode(code, wxShopAppId, wxShopAppSecret);
    }

    public void logoutByOpenId() {
        SysAccount currentAccount = UtilsAccount.getCurrentAccount(true);

        currentAccount.setOpenId(null);

        sysAccountService.update(currentAccount, Sets.newHashSet("openId"));
    }

    public List<String> control(HttpServletRequest request, Integer version) {
        String currentSystem = request.getHeader("lt-request-config-system");

        SysDict appletControlDict = sysDictService.getOne(new SysDict().setCode(StrUtil.join("", currentSystem, "AppletControl-", version)));

        if(ObjectUtil.isNull(appletControlDict)) return Lists.newArrayList();

        String remark = appletControlDict.getRemark();

        if(StrUtil.isBlank(remark)) return Lists.newArrayList();

        String[] codes = remark.split(",");

        return Lists.newArrayList(codes);
    }
}
